package cz.kralicko.main.dao.impl;

import java.lang.reflect.ParameterizedType;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.proxy.HibernateProxy;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import cz.kralicko.main.dao.GenericDao;



public class AbstractDaoHibernate<T> implements GenericDao<T>, InitializingBean {

	/** Hibernate's session factory. */
	@Autowired
	private SessionFactory sessionFactory;

	/** The Spring <code>HibernateDaoSupport</code>. */
	private HibernateDaoSupport hibernateDao;

	/** Class of target entity. */
	private final Class<T> persistentClass;

	@Override
	public T getFirst() {
		return getAll().get(0);
	}

	@Override
	public T getLast() {
		List<T> list = getAll();
		return list.get(list.size() - 1);
	}

	/**
	 * Constructor. Resolves actual type of persistent class.
	 */
	@SuppressWarnings("unchecked")
	public AbstractDaoHibernate() {
		this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
	}

	/**
	 * Getter for actual class.
	 * 
	 * @return actual class for this instance
	 */
	public Class<T> getPersistentClass() {
		return this.persistentClass;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void delete(final T entity) {
		getHibernateTemplate().delete(entity);
	}

	/** {@inheritDoc} */
	@Override
	public T getById(final long id){
		return getHibernateTemplate().get(getPersistentClass(), id);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public List<T> getAll() {
		return getHibernateTemplate().loadAll(getPersistentClass());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public List<T> getAll(final String sortedBy) {
		HibernateCallback<List> callback = new HibernateCallback<List>() {
			@Override
			public List doInHibernate(final Session session)
					throws HibernateException, SQLException {
				final Criteria crit = session
						.createCriteria(getPersistentClass());
				crit.addOrder(Order.asc(sortedBy));
				return crit.list();
			}
		};

		return getHibernateTemplate().executeFind(callback);
	}

	/**
	 * {@inheritDoc}
	 **/
	@Override
	public T store(final T entity) {
		try {
			getHibernateTemplate().saveOrUpdate(entity);
		} catch (DataIntegrityViolationException d) {
			return null;
		}
		return entity;
	}

	/** {@inheritDoc} */
	@Override
	public T merge(final T entity) {
		return getHibernateTemplate().merge(entity);
	}

	/** {@inheritDoc} */
	@Override
	public T save(final T entity) {
		getHibernateTemplate().save(entity);
		return entity;
	}

	/** {@inheritDoc} */
	@Override
	public void flush() {
		getHibernateTemplate().flush();
	}

	/** {@inheritDoc} */
	@Override
	public void evict(final Object entity) {
		getHibernateTemplate().evict(entity);
	}

	/** {@inheritDoc} */
	@Override
	@SuppressWarnings("rawtypes")
	public Integer count() {
		HibernateCallback<Integer> callback = new HibernateCallback<Integer>() {
			@Override
			public Integer doInHibernate(final Session session)
					throws HibernateException, SQLException {
				final Criteria crit = session
						.createCriteria(getPersistentClass());
				crit.setProjection(Projections.rowCount());
				List result = crit.list();
				return (Integer) result.get(0);
			}
		};

		return getHibernateTemplate().execute(callback);
	}

	/** {@inheritDoc} */
	@Override
	public T deproxy(final Object maybeProxy) throws ClassCastException {
		final T result;
		if (maybeProxy instanceof HibernateProxy) {
			result = getPersistentClass().cast(
					((HibernateProxy) maybeProxy).getHibernateLazyInitializer()
							.getImplementation());
		} else {
			result = getPersistentClass().cast(maybeProxy);
		}

		return result;
	}

	/** {@inheritDoc} */
	@Override
	public <S> S deproxy(final Object maybeProxy, final Class<S> baseClass)
			throws ClassCastException {
		final S result;
		if (maybeProxy instanceof HibernateProxy) {
			result = baseClass.cast(((HibernateProxy) maybeProxy)
					.getHibernateLazyInitializer().getImplementation());
		} else {
			result = baseClass.cast(maybeProxy);
		}

		return result;
	}

	/**
	 * Gets the Hibernate template.
	 * 
	 * @return the Hibernate template
	 */
	protected final HibernateTemplate getHibernateTemplate() {
		final HibernateTemplate template = this.hibernateDao
				.getHibernateTemplate();
		adjustHibernateTemplate(template);
		return template;
	}

	/**
	 * Adjusts the Hibernate Template associated with this DAO. This method can
	 * be overridden in specialized DAOs to set attributes of the template.
	 * 
	 * @param template
	 *            the template to be adjusted
	 */
	protected void adjustHibernateTemplate(final HibernateTemplate template) {
		template.setCacheQueries(true);
	}

	/**
	 * Current session getter.
	 * 
	 * @return current session
	 */
	protected Session getSession() {
		return this.hibernateDao.getSessionFactory().getCurrentSession();
	}

	// ------------------------------------------------- InitializingBean Stuff

	/** {@inheritDoc} */
	@Override
	public void afterPropertiesSet() throws Exception {
		this.hibernateDao = new HelperHibernateDaoSupport();
		this.hibernateDao.setSessionFactory(this.sessionFactory);
	}

	@Override
	public void deleteAll(Collection<T> collection) {
		if (collection == null) {
			throw new IllegalArgumentException("Missing agrument!");
		}
		this.hibernateDao.getHibernateTemplate().deleteAll(collection);
	}

	/**
	 * Just to be able to construct an instance of
	 * <code>HibernateDaoSupport</code> which is abstract.
	 */
	public static class HelperHibernateDaoSupport extends HibernateDaoSupport {
	}

}